home *** CD-ROM | disk | FTP | other *** search
- /*
- * tkUnixNotify.c --
- *
- * This file provides the platform specific event detection
- * facilities used by the Tk event routines. The procedures in
- * this file comprise the notifier interface and contain unix
- * specific file and timer handling code.
- *
- * Copyright (c) 1995 Sun Microsystems, Inc.
- *
- * See the file "license.terms" for information on usage and redistribution
- * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
- */
-
- static char sccsid[] = "@(#) tkUnixNotify.c 1.22 95/10/03 13:51:51";
-
- #include "tkPort.h"
- #include "tkInt.h"
- #include <signal.h>
-
- /*
- * The information below is used to provide read, write, and
- * exception masks to select during calls to Tk_DoOneEvent.
- */
-
- static fd_mask ready[3*MASK_SIZE];
- /* Masks passed to select and modified
- * by kernel to indicate which files are
- * actually ready. */
- static fd_mask check[3*MASK_SIZE];
- /* Current set of masks, built up by
- * Tk_NotifyFile and Tk_NotifyDisplay,
- * that reflects what files we should wait
- * for in the next select. */
- static int numFdBits = 0; /* Number of valid bits in mask
- * arrays (this value is passed
- * to select). This value is only a high
- * water mark as long as at least one display
- * is registered. If there are no displays,
- * then it is recomputed at the end of each
- * call to Tk_DoOneEvent. */
-
- /*
- * For each display that Tk has called Tk_NotifyDisplay, there is one
- * record of the following type, chained together in a list.
- */
-
- typedef struct DisplayList {
- Display *display; /* Display from which events may arrive. */
- fd_mask *readPtr; /* Pointer to word in ready array
- * for this display's read mask bit. */
- fd_mask *checkReadPtr; /* Pointer to word in check array
- * for this display's read mask bit. */
- fd_mask bitSelect; /* Value to AND with *readPtr etc. to
- * select just this file's bit. */
- struct DisplayList *nextPtr;
- /* Next display in list, nor NULL for
- * end of queue. */
- } DisplayList;
-
- static DisplayList *firstDisplayPtr;
- /* First display in list of registered */
- /* displays. */
-
- /*
- * The notifier has at most one pending timer. The next scheduled timer
- * event is recorded in the pendingTimeout structure. The
- * pendingTimeoutPtr is set to refer to pendingTimeout by Tk_NotifyTimer.
- * Once the timer fires, Tk_DoOneEvent resets the pointer to NULL.
- */
-
- static Tk_Time *pendingTimeoutPtr;
- /* Points to current timer value. */
- static Tk_Time pendingTimeout; /* Time when timer should fire. */
-
- /*
- * Prototypes for procedures referenced only in this file:
- */
-
- static int ServiceDisplay _ANSI_ARGS_((Display *display));
- static void RefreshFdBits _ANSI_ARGS_((void));
-
- /*
- *----------------------------------------------------------------------
- *
- * ServiceDisplay --
- *
- * This function is called by Tk_DoOneEvent whenever the notifier
- * detects that the Display's fd is readable. It is responsible
- * for retrieving and queueing any pending X events.
- *
- * Results:
- * The return value is 1 if the procedure found an event to
- * queue. If no events were found, then 0 is returned.
- *
- * Side effects:
- * Passes all pending X events to Tk_QueueEvent.
- *
- *----------------------------------------------------------------------
- */
-
- static int
- ServiceDisplay(display)
- Display *display; /* Display to service. */
- {
- Tk_Event event;
- int numFound;
-
- /*
- * We should not need to do a flush of the output queues before
- * calling XEventsQueued because it should have been done before
- * we called select in Tk_DoOneEvent.
- */
-
- numFound = XEventsQueued(display, QueuedAfterReading);
- if (numFound == 0) {
-
- /*
- * Things are very tricky if there aren't any events readable
- * at this point (after all, there was supposedly data
- * available on the connection). A couple of things could
- * have occurred:
- *
- * One possibility is that there were only error events in the
- * input from the server. If this happens, we should return
- * (we don't want to go to sleep in XNextEvent below, since
- * this would block out other sources of input to the
- * process).
- *
- * Another possibility is that our connection to the server
- * has been closed. This will not necessarily be detected in
- * XEventsQueued (!!), so if we just return then there will be
- * an infinite loop. To detect such an error, generate a NoOp
- * protocol request to exercise the connection to the server,
- * then return. However, must disable SIGPIPE while sending
- * the request, or else the process will die from the signal
- * and won't invoke the X error function to print a nice (?!)
- * message.
- */
-
- void (*oldHandler)();
-
- oldHandler = (void (*)()) signal(SIGPIPE, SIG_IGN);
- XNoOp(display);
- XFlush(display);
- (void) signal(SIGPIPE, oldHandler);
- return 0;
- }
-
- /*
- * Transfer events from the X event queue to the Tk event queue.
- */
-
- event.type = TK_WINDOW_EVENTS;
- while (numFound > 0) {
- XNextEvent(display, &event.window.event);
- Tk_QueueEvent(&event, TK_QUEUE_TAIL);
- numFound--;
- }
- return 1;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tk_NotifyFile --
- *
- * Arrange for Tk_QueueEvent to be called the next time a given
- * I/O channel becomes readable or writable. This is a one-shot
- * event.
- *
- * Results:
- * None.
- *
- * Side effects:
- * The notifier will generate a file event when the I/O channel
- * given by fd next becomes ready in the way indicated by mask.
- * If fd is already registered then the old mask will be replaced
- * with the new one. Once the event is sent, the notifier will
- * not send any more events about the fd until the next call to
- * Tk_NotifyFile.
- *
- *----------------------------------------------------------------------
- */
-
- void
- Tk_NotifyFile(fd, mask)
- int fd; /* Integer identifier for a stream. */
- int mask; /* OR'ed combination of TK_READABLE,
- * TK_WRITABLE, and TK_EXCEPTION:
- * indicates conditions under which a
- * new event should be queued. */
- {
- int index;
- fd_mask bitSelect;
-
- if (fd >= FD_SETSIZE) {
- panic("Tk_NotifyFile can't handle file id %d", fd);
- }
-
- /*
- * Make sure the appropriate check bits are set for the specified file
- * descriptor.
- */
-
- index = fd/(NBBY*sizeof(fd_mask));
- bitSelect = 1 << (fd%(NBBY*sizeof(fd_mask)));
- if (mask & TK_READABLE) {
- check[index] |= bitSelect;
- } else {
- check[index] &= ~bitSelect;
- }
- if (mask & TK_WRITABLE) {
- check[index+MASK_SIZE] |= bitSelect;
- } else {
- check[index+MASK_SIZE] &= ~bitSelect;
- }
- if (mask & TK_EXCEPTION) {
- check[index+2*MASK_SIZE] |= bitSelect;
- } else {
- check[index+2*MASK_SIZE] &= ~bitSelect;
- }
-
- if (numFdBits <= fd) {
- numFdBits = fd+1;
- }
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tk_IgnoreFile --
- *
- * Cancel an outstanding file event request.
- *
- * Results:
- * None.
- *
- * Side effects:
- * The notifier will stop looking for events on the I/O channel
- * given by fd.
- *
- *----------------------------------------------------------------------
- */
-
- void
- Tk_IgnoreFile(fd)
- int fd; /* Integer identifier for a stream. */
- {
- int index;
- fd_mask bitSelect;
-
- /*
- * Turn off check bits for the specified file.
- */
-
- index = fd/(NBBY*sizeof(fd_mask));
- bitSelect = 1 << (fd%(NBBY*sizeof(fd_mask)));
- check[index] &= ~bitSelect;
- check[index+MASK_SIZE] &= ~bitSelect;
- check[index+2*MASK_SIZE] &= ~bitSelect;
-
- /*
- * If this fd was at the high water mark and there is a chance
- * that it was the last fd, then recompute the numFdBits.
- */
-
- if ((firstDisplayPtr != NULL) && (fd == (numFdBits-1))) {
- RefreshFdBits();
- }
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tk_NotifyDisplay --
- *
- * Arrange for events to be generated for a particular display.
- *
- * Results:
- * None.
- *
- * Side effects:
- * From now on, all window system events directed at display will
- * be passed to Tk_QueueEvent.
- *
- *----------------------------------------------------------------------
- */
-
- void
- Tk_NotifyDisplay(display)
- Display *display; /* Display for which events should be
- * generated. */
- {
- int index, fd;
- DisplayList *displayPtr;
-
- /*
- * Ignore duplicate displays.
- */
-
- for (displayPtr = firstDisplayPtr; displayPtr != NULL;
- displayPtr = displayPtr->nextPtr) {
- if (displayPtr->display == display) {
- return;
- }
- }
-
- /*
- * Add the display to the display list.
- */
-
- fd = ConnectionNumber(display);
- displayPtr = (DisplayList *) ckalloc(sizeof(DisplayList));
- displayPtr->display = display;
- index = fd/(NBBY*sizeof(fd_mask));
- displayPtr->readPtr = &ready[index];
- displayPtr->checkReadPtr = &check[index];
- displayPtr->bitSelect = 1 << (fd%(NBBY*sizeof(fd_mask)));
- displayPtr->nextPtr = firstDisplayPtr;
- firstDisplayPtr = displayPtr;
-
- check[index] |= displayPtr->bitSelect;
-
- if (numFdBits <= fd) {
- numFdBits = fd+1;
- }
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tk_IgnoreDisplay --
- *
- * Arrange for events to no longer be delivered for a particular
- * display.
- *
- * Results:
- * None.
- *
- * Side effects:
- * From now on, the notifier will not generate events for a
- * particular display.
- *
- *----------------------------------------------------------------------
- */
-
- void
- Tk_IgnoreDisplay(display)
- Display *display; /* Display for which events should not
- * be generated. */
- {
- int index, fd;
- DisplayList *displayPtr, *prevPtr;
- fd_mask bitSelect;
-
- for (displayPtr = firstDisplayPtr, prevPtr = NULL; displayPtr != NULL;
- prevPtr = displayPtr, displayPtr = displayPtr->nextPtr) {
- if (displayPtr->display != display) {
- continue;
- }
- if (prevPtr == NULL) {
- firstDisplayPtr = displayPtr->nextPtr;
- } else {
- prevPtr->nextPtr = displayPtr->nextPtr;
- }
- fd = ConnectionNumber(display);
- index = fd/(NBBY*sizeof(fd_mask));
- bitSelect = 1 << (fd%(NBBY*sizeof(fd_mask)));
- check[index] &= ~bitSelect;
- ckfree((char *) displayPtr);
-
- /*
- * If this fd was at the high water mark and there is a chance
- * that it was the last fd, then recompute the numFdBits.
- */
-
- if ((firstDisplayPtr != NULL) && (fd == (numFdBits-1))) {
- RefreshFdBits();
- }
-
- return;
- }
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tk_NotifyTimer --
- *
- * Arrange for an event to be generated at a particular time in
- * the future. This is a one-shot event.
- *
- * Results:
- * None.
- *
- * Side effects:
- * When the time specified by timeout is reached, a timer event
- * will be passed to Tk_QueueEvent. There is only one
- * outstanding timer, so if the previous timer has not expired,
- * it will be replaced with the current timeout value. Only one
- * timer event will be generated after the timer expires until
- * the next call to Tk_NotifyTimer.
- *
- *----------------------------------------------------------------------
- */
-
- void
- Tk_NotifyTimer(time)
- Tk_Time *time; /* Absolute time when the notifier should
- * generate a timer event. */
- {
- pendingTimeoutPtr = &pendingTimeout;
- pendingTimeout = *time;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tk_DoOneEvent --
- *
- * Process a single event of some sort. If there's no work to
- * do, wait for an event to occur, then process it.
- *
- * Results:
- * The return value is 1 if the procedure actually found an event
- * to process. If no processing occurred, then 0 is returned.
- * The caller should invoke Tk_DoOneEvent repeatedly until it
- * returns 0 to insure that all events have been processed.
- *
- * Side effects:
- * May delay execution of process while waiting for an event,
- * unless TK_DONT_WAIT is set in the flags argument.
- * Tk_ServiceEvent will be called at most once. If no queued
- * events were processed, then the procedure checks for new
- * events. If TK_DONT_WAIT is set or Tk has called
- * Tk_NotifyIdle since the last time Tk_ServiceIdle was called,
- * then Tk_DoOneEvent will only poll for events, otherwise it
- * blocks until the next event occurs. All detected events will
- * be queued. If no events are detected, and Tk has called
- * Tk_NotifyIdle, then Tk_ServiceIdle will be called.
- *
- *----------------------------------------------------------------------
- */
-
- int
- Tk_DoOneEvent(flags)
- int flags; /* Miscellaneous flag values: may be any
- * combination of TK_DONT_WAIT,
- * TK_WINDOW_EVENTS, TK_FILE_EVENTS,
- * TK_TIMER_EVENTS, and TK_IDLE_EVENTS. */
- {
- DisplayList *displayPtr;
- struct timeval curTime, timeout, *timeoutPtr;
- Tk_Event event;
- fd_mask fdmask, bitSelect;
- int index, numFound;
- int foundEvent = 0;
- int dontLoop; /* Avoid reentering select if set to 1. */
-
- /*
- * No event flags is equivalent to TK_ALL_EVENTS.
- */
-
- if ((flags & TK_ALL_EVENTS) == 0) {
- flags |= TK_ALL_EVENTS;
- }
-
- dontLoop = (flags & TK_DONT_WAIT);
-
- /*
- * Look for events until we find something to do, or no other events
- * are possible.
- */
-
- do {
-
- /*
- * The first thing we do is to service any asynchronous event
- * handlers.
- */
-
- if (Tcl_AsyncReady()) {
- (void) Tcl_AsyncInvoke((Tcl_Interp *) NULL, 0);
- return 1;
- }
-
- /*
- * Ask Tk to service a queued event. If Tk does not handle any events,
- * then we should look for new events.
- */
-
- if (Tk_ServiceEvent(flags)) {
- return 1; /* Tk serviced an event so we're done. */
- }
-
- /*
- * Skip to the idle handlers if that is all we are doing. Since
- * nothing else can happen, we set dontLoop to 1.
- */
-
- if ((flags & TK_ALL_EVENTS) == TK_IDLE_EVENTS) {
- dontLoop = 1;
- goto doIdle;
- }
-
- /*
- * First we need to check for X events which are already queued.
- */
-
- if ((flags & TK_WINDOW_EVENTS)) {
- for (displayPtr = firstDisplayPtr; displayPtr != NULL;
- displayPtr = displayPtr->nextPtr) {
- XFlush(displayPtr->display);
- if (XQLength(displayPtr->display) > 0) {
- if (ServiceDisplay(displayPtr->display)) {
- foundEvent = 1;
- }
- }
- }
- }
-
- /*
- * Load the select mask from the current check mask.
- */
-
- memcpy((VOID *) ready, (VOID *) check, 3*MASK_SIZE*sizeof(fd_mask));
-
- /*
- * Compute the current timeout value. If we have already detected an
- * event, if idle tasks are pending or if TK_DONT_WAIT is set, then we
- * should poll. If a timer is pending, then we should compute the
- * remaining time. Otherwise, we block.
- */
-
- if (foundEvent
- || (flags & TK_DONT_WAIT)
- || ((flags & TK_IDLE_EVENTS) && Tk_IdlePending())) {
- timeout.tv_sec = 0;
- timeout.tv_usec = 0;
- timeoutPtr = &timeout;
- } else if ((pendingTimeoutPtr != NULL) && (flags & TK_TIMER_EVENTS)) {
- (void) gettimeofday(&curTime, (struct timezone *) NULL);
-
- /*
- * Check to see if the timer has expired.
- */
-
- if ((pendingTimeoutPtr->sec < curTime.tv_sec)
- || ((pendingTimeoutPtr->sec == curTime.tv_sec)
- && (pendingTimeoutPtr->usec < curTime.tv_usec))) {
- timeout.tv_sec = 0;
- timeout.tv_usec = 0;
- } else {
- timeout.tv_sec = pendingTimeoutPtr->sec - curTime.tv_sec;
- timeout.tv_usec = pendingTimeoutPtr->usec - curTime.tv_usec;
- if (timeout.tv_usec < 0) {
- timeout.tv_sec -= 1;
- timeout.tv_usec += 1000000;
- }
- }
- timeoutPtr = &timeout;
- } else {
- timeoutPtr = NULL;
- }
-
- /*
- * If no events could be detected, return immediately to
- * avoid blocking forever.
- */
-
- if ((timeoutPtr == NULL) && (numFdBits == 0)) {
- return 0;
- }
-
- /*
- * Wait until an event occurs or the timer expires.
- */
-
- numFound = select(numFdBits, (SELECT_MASK *) &ready[0],
- (SELECT_MASK *) &ready[MASK_SIZE],
- (SELECT_MASK *) &ready[2*MASK_SIZE], timeoutPtr);
-
- /*
- * Some systems don't clear the masks after an error, so
- * we have to do it here.
- */
-
- if (numFound == -1) {
- memset((VOID *) ready, 0, 3*MASK_SIZE*sizeof(fd_mask));
- }
-
- /*
- * Check to see if the timer has expired.
- */
-
- if ((pendingTimeoutPtr != NULL) && (flags & TK_TIMER_EVENTS)) {
- (void) gettimeofday(&curTime, (struct timezone *) NULL);
- if ((pendingTimeoutPtr->sec < curTime.tv_sec)
- || ((pendingTimeoutPtr->sec == curTime.tv_sec)
- && (pendingTimeoutPtr->usec < curTime.tv_usec))) {
- pendingTimeoutPtr = NULL;
-
- /*
- * Generate timer event.
- */
-
- foundEvent = 1;
- event.type = TK_TIMER_EVENTS;
- event.timer.time.sec = curTime.tv_sec;
- event.timer.time.usec = curTime.tv_usec;
- Tk_QueueEvent(&event, TK_QUEUE_TAIL);
- }
- }
-
- /*
- * Check for window system events. Once a display has been serviced,
- * it is removed from the ready mask, so we don't generate a file event
- * for it.
- */
-
- for (displayPtr = firstDisplayPtr; displayPtr != NULL;
- displayPtr = displayPtr->nextPtr) {
- if (*displayPtr->readPtr & displayPtr->bitSelect) {
- if (ServiceDisplay(displayPtr->display)) {
- foundEvent = 1;
- }
- numFound--;
- *displayPtr->readPtr &= ~(displayPtr->bitSelect);
- }
- }
-
- /*
- * Check for file events. We iterate over the ready fd_masks looking
- * for a mask with any bits set. By or'ing the read, write, and
- * exception masks together, we can make a single pass through the file
- * descriptors. If any event occured on a file, we need to queue a
- * file event with the appropriate file descriptor and event mask, and
- * then clear all of the check bits for that file.
- */
-
- if (numFound > 0) {
- foundEvent = 1;
- event.type = TK_FILE_EVENTS;
- index = -1;
- bitSelect = 0;
- event.file.fd = 0;
- fdmask = 0;
-
- /*
- * Each time through the loop we shift bitSelect. When we
- * have tested each bit in the current mask, bitSelect becomes
- * zero, so we know to continue with the next mask in the set.
- */
-
- while (numFound > 0) {
- if (!bitSelect) { /* check for wrap-around */
- if (event.file.fd >= numFdBits) {
- /*
- * Somehow there aren't as many files ready as numFound
- * suggests. Either select lied to us or there is a
- * reentrancy problem here.
- */
-
- panic("Tk_DoOneEvent got bogus count from select");
- }
- bitSelect = 1;
- index++;
- fdmask = ready[index] | ready[index+MASK_SIZE]
- | ready[index+2*MASK_SIZE];
- }
- if (fdmask & bitSelect) {
- numFound--;
- event.file.mask = 0;
- if (ready[index] & bitSelect) {
- event.file.mask |= TK_READABLE;
- }
- if (ready[index+MASK_SIZE] & bitSelect) {
- event.file.mask |= TK_WRITABLE;
- }
- if (ready[index+2*MASK_SIZE] & bitSelect) {
- event.file.mask |= TK_EXCEPTION;
- }
- Tk_QueueEvent(&event, TK_QUEUE_TAIL);
- check[index] &= ~bitSelect;
- check[index+MASK_SIZE] &= ~bitSelect;
- check[index+2*MASK_SIZE] &= ~bitSelect;
- }
- bitSelect <<= 1;
- event.file.fd++;
- }
-
- /*
- * If there are no displays currently registered, then we need to
- * recompute numFdBits since some bits have been cleared and we may
- * block the next time through.
- */
-
- if (firstDisplayPtr == NULL) {
- RefreshFdBits();
- }
- }
-
- /*
- * If no other events were generated, then we should try to
- * service pending idle handlers.
- */
-
- doIdle:
- if (!foundEvent && (flags & TK_IDLE_EVENTS)) {
- foundEvent = Tk_ServiceIdle();
- }
-
- } while (!foundEvent && !dontLoop);
-
- /*
- * No other events can be detected at this point.
- */
-
- return foundEvent;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tk_Sleep --
- *
- * Delay execution for the specified number of milliseconds.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Time passes.
- *
- *----------------------------------------------------------------------
- */
-
- void
- Tk_Sleep(ms)
- int ms; /* Number of milliseconds to sleep. */
- {
- static struct timeval delay;
-
- delay.tv_sec = ms/1000;
- delay.tv_usec = (ms%1000)*1000;
- (void) select(0, (SELECT_MASK *) 0, (SELECT_MASK *) 0,
- (SELECT_MASK *) 0, &delay);
- }
-
- /*
- *--------------------------------------------------------------
- *
- * Tk_MainLoop --
- *
- * Call Tk_DoOneEvent over and over again in an infinite
- * loop as long as there exist any main windows.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Arbitrary; depends on handlers for events.
- *
- *--------------------------------------------------------------
- */
-
- void
- Tk_MainLoop()
- {
- while (Tk_GetNumMainWindows() > 0) {
- Tk_DoOneEvent(0);
- }
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * RefreshFdBits --
- *
- * This function finds the largest currently registered
- * file descriptor from the check mask.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Updates the numFdBits static variable.
- *
- *----------------------------------------------------------------------
- */
-
- static void
- RefreshFdBits()
- {
- int limit, index;
- fd_mask fdmask;
-
- /*
- * We start by finding the largest element of the check array that
- * contains at least one set bit. We start the search with the
- * last known maximum index value.
- */
-
- limit = (numFdBits / (NBBY*sizeof(fd_mask)));
- numFdBits = 0;
- fdmask = 0;
- for (index = limit; (index >= 0); index--) {
- fdmask = check[index] | check[index+MASK_SIZE]
- | check[index+2*MASK_SIZE];
- if (fdmask) {
- break;
- }
- }
-
- /*
- * Now that we have the biggest mask, we need to find the most
- * significant bit.
- */
-
- if (fdmask) {
- int offset;
- numFdBits = index*NBBY*sizeof(fd_mask);
- for (offset = 0; ; offset++) {
- fdmask &= ~(1<<offset);
- if (!fdmask) {
- break;
- }
- }
- numFdBits += (offset+1);
- }
- }
-